# 深度剖析JavaScript ES5/AMD/CMD/COMMONJS/ES6模块化

前端模块化其实是一个有年代知识的考点,面试中经常会问到各种模块化标准之间的异同,其中一些模块化标准至今已经被废弃不再使用,一些模块化至今仍然在实际应用中发光发热。

本文尝试详解各种前端模块化标准。

# CommonJS

CommonJS 出现就是为了解决原本 JS 文件引入的不足点: 模块依赖全局污染 问题,它并不是 JS 本身的一个内容,而是对于模块化的一种具体实现。

  • CommonJS 实际上就是用 require 函数,只要引用,就会创建一个 模块的实例 ,即实例化(每当引入一个文件,就会为这个文件变成一个 JS 实例);
  • 做法是通过 require 引入模块,通过 module.exports 导出,并且文件加载是 同步 完成的;
  • 对服务端比较友好,内涵 缓存机制 ,也就是说只要 require 一个模块,那么这个模块就会被缓存,并且还会进行一次比较异同的操作,例如我修改了这个模块,那么就会 将缓存的模块替换新的模块
  • 在 node 上运行,不依赖于客户端。

而当我们引入一个模块后,其实会被解析为一个立即执行函数,类似原本 ES5 利用自执行函数封装一样,类似下面, 引入的模块并不是全局变量。

(function(exports, require, module, __filename, __dirname) {
  
})()

CommonJS 至今仍然是 nodejs 的模块化标准。

# AMD 出现

看到 AMD,你可能会情不自禁地说 AMD YES!但此 AMD 并非彼 AMD。

从上问我们知道了 CommonJS 只能在 node 环境下使用,在浏览器上无法使用,而 CommonJS 本身的实现思路是不错的,于是创造了 AMD(Asynchronous Module Definition)异步模块定义,是通过 RequireJS 来实现,区别于 CommonJS,它是 异步 的。

定义模块: define(moduleName, [module], factory)

引入模块: require([module], callback)

下面看这个例子

img

moduleB 此时它依赖于模块 A,按照以下方式进行依赖。

img

然后在 index.js 文件中我们需要通过 require.config 来配置我们的模块路径,如若没有配置的话,就会报错找不到。

img

注意点! 上述依赖模块 只有当全部加载完毕之后,才会执行后边的回调函数。 这种方式就是我们比较疑惑的一个名词: 依赖前置。

因此,它不用考虑模块的引入顺序,并且保证是规范化的输入输出,同时它会动态地创建 script 标签,如下图所示,它使用了 async 来进行异步加载。

img

# 引出 CMD

此时,阿里也为模块化作了贡献,推出了 CMD(Common Module Definition)通用模块定义,它参考了 AMD,引入了 sea.js

定义模块: define(function (require, exports, module) {})

引入模块: sea.js([module路径], function(moduleA, moduleB, moduleC) {})

下面看个例子:

定义一个模块 moduleA

img

再来定义一个模块 moduleB,此时它依赖于模块 A,按照如下方式进行依赖。

img

然后在 index.js 文件中,我们需要首先通过数组确定模块路径,如下所示:

seajs.use(['module_a.js', 'module_b.js'], function(moduleA, moduleB){
    console.log(moduleA.a);
    console.log(moduleB.b);
})

下面总结一下 CMD 相关知识:

通过 require 加载,define 定义,exports 导出(使用 return 同理),module 操作模块。

而在使用模块时,需要配置相关 url,依赖加载完毕之后,再执行回调函数,这里和 AMD 没啥区别。

注意点! 但是下面就有本质上不同了, CMD 就进依赖、按需加载, 增强灵活性。

# ES6 模块化登场

JS 语言标准中的模块化规范,属于语言本身的一种新特性。

引入模块: import module from '模块路径'

导出模块: export module

下面看这个例子:

定义一个模块 moduleA

img

在定义一个模块 moduleB,此时它依赖于模块 A,按照以下方式进行依赖。

img

然后在 index.js 文件中我们需要配置我们的模块路径,如若没有配置的话,就会报错找不到。 img

ES6 模块化 与 CommonJS 对比:

先做一个实验,假设

在 export.js 中我们导出一个变量 a,并且进行了自加操作:

img

在 common.js 文件中,我们写入如下代码:

img

在 es6.js 文件中,我们写入如下代码:

img

最终结果如下: img

通过例子,我们来总结一下:

区别1:

  • common.js 模块输出的是一个值的拷贝
  • ES6 模块输出的是一个值得引用

区别2:

  • common.js 运行在服务端,因此模块是在运行时加载,即程序执行时 require 才会加载。
  • ES6 模块在编译时加载